/*
  Copyright (c) 2010 Les Newell. All rights reserved

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program. If not, see <http://www.gnu.org/licenses/>.
*/


#include "wx_pch.h"

#include "Serial.h"

IMPLEMENT_DYNAMIC_CLASS(wxStringEvent, wxCommandEvent);

DEFINE_LOCAL_EVENT_TYPE(wxEVT_LIST_RECEIVED)
DEFINE_LOCAL_EVENT_TYPE(wxEVT_LOG)
DEFINE_LOCAL_EVENT_TYPE(wxEVT_CONNECT)


void Serial::Notify()
{
    if (!IsOpen())
    {
        return;
    }
    CheckSerial();
}


bool Serial::ReadSerial (wxString& target, const int timeout)
{
    wxStopWatch watch;
    watch.Start();
    SERIAL_RESULT result;
    do
    {
        result = CheckSerial(&target);
    }
    while (result == resNODATA &&
            watch.Time() < timeout);
    return(result == resOK);
}

Serial::SERIAL_RESULT Serial::CheckSerial(wxString * target)
{
    char buf;
    int bytes = m_port.Read(&buf,1);
    if (bytes == 0)
    {
        return(resNODATA);
    }
    if (bytes < 0)
    {
        Close();
        return(resERROR);
    }
    m_port.PutBack(buf);
    wxString line;
    if ( 0 == ReadUntilEOS(line))
    {
        return(resERROR);
    }
    if (line[0] == _T('*')) //signal list
    {
//wxLogMessage(_T("*"));
        m_waiting = false;
        if(m_running)
        {
            if (m_evtTarget)
            {
                wxStringEvent newEvt(wxEVT_LIST_RECEIVED, 0);
                newEvt.SetString(line.Mid(1));
                m_evtTarget->ProcessEvent(newEvt);
            }
            SendR();
        }
        return(resNODATA);
    }
    if (m_evtTarget)
    {
        wxStringEvent newEvt(wxEVT_LOG, 0);
        newEvt.SetString(line);
        m_evtTarget->ProcessEvent(newEvt);
    }
    if (line[0] == _T('#')) //comment
    {
        return(resNODATA);
    }
    if (line.Left(6) == _T("ERROR:"))
    {
        m_errorCount ++;
        if(m_errorCount < 5)
        {
            wxMessageBox(line.Mid(6),_("Serial terminal"),wxICON_ERROR);
            m_errorCount--;
        }
        return(resNODATA);
    }
    if (target)
    {
        *target = line;
    }
    return(resOK);
}


bool Serial::Send(const wxString& text, wxArrayString& response)
{
    m_running = false;
    if (!RawSend(text))
    {
        return(false);
    }
    bool ok = true;
    while (ok)
    {
        wxString buf;
        ok = ReadSerial(buf);
        if (!ok)
        {
            break;
        }
        if (buf == _T("ok"))
        {
            return(true);
        }
        response.Add(buf);
    }
    return(false);
}

bool Serial::Send(const wxString& text, wxString& response, const int timeout)
{
    m_running = false;
    CheckSerial();
    if (!RawSend(text))
    {
        return(false);
    }
    if(!ReadSerial(response,timeout))
    {
        return(false);
    }
    wxString buf;
    if(!ReadSerial(buf))
    {
        return(false);
    }
    return(buf == _T("ok"));
}

bool Serial::Send(const wxString& text, const int timeout)
{
    m_running = false;
    CheckSerial();
    if (!RawSend(text))
    {
        return(false);
    }
    wxString buf;
    if(!ReadSerial(buf,timeout))
    {
        return(false);
    }
    return(buf == _T("ok"));
}

bool Serial::RawSend(const wxString& text, const bool log)
{
    if (!m_port.IsOpen())
    {
        if (!Open())
        {
            return(false);
        }
    }
    wxStopWatch s;
    s.Start();
    while(m_waiting && s.Time() < 500)
    {
        CheckSerial();
    }
    if(s.Time() >= 500)
    {
        wxLogMessage(_("Timed out waiting for value list"));
        m_waiting = false;
    }

    const wxCharBuffer b = text.mb_str();
    m_port.Write(b,strlen(b));
    const char eos = '\n';
    m_port.Write(&eos,1);
    if(!log)
    {
        return(true);
    }
    if (m_evtTarget)
    {
        wxStringEvent newEvt(wxEVT_LOG, 0);
        newEvt.SetString(_T(">") + text);
        m_evtTarget->ProcessEvent(newEvt);
    }
    return(true);
}

bool Serial::ReadUntilEOS(wxString& string)
{
    size_t bytes;
    char * data;
    int ret = m_port.ReadUntilEOS(data,&bytes);
    if (ret == 0)
    {
        wxLogMessage(_T("Timed out waiting for a response"));
        return(false);
    }
    if (ret <0)
    {
        wxLogMessage(_T("Communication error"));
        Close();
        return(false);
    }
    if (bytes ==0)
    {
        string = _T("");
    }
    else
    {
        data[bytes - 1] = 0;
        string = wxString::FromAscii(data);
        delete[] data;
    }
    return(true);
}

void Serial::SignalState(bool state)
{
    m_list.Clear();
    if (m_evtTarget)
    {
        wxCommandEvent newEvt(wxEVT_CONNECT, 0);
        newEvt.SetInt(state);
        m_evtTarget->ProcessEvent(newEvt);
    }
}

static const wxBaud g_bauds[]=
{
    wxBAUD_150,
    wxBAUD_300,
    wxBAUD_600,
    wxBAUD_1200,
    wxBAUD_2400,
    wxBAUD_4800,
    wxBAUD_9600,
    wxBAUD_19200,
    wxBAUD_38400,
    wxBAUD_57600,
    wxBAUD_115200,
    wxBAUD_230400,
    wxBAUD_460800,
    wxBAUD_921600,
};

static const char * g_ports[]=
{
    wxCOM1,
    wxCOM2,
    wxCOM3,
    wxCOM4,
    wxCOM5,
    wxCOM6,
    wxCOM7,
    wxCOM8,
    wxCOM9,
    wxCOM10,
    wxCOM11,
    wxCOM12,
    wxCOM13,
    wxCOM14,
    wxCOM15,
    wxCOM16,
};


bool Serial::Open()
{
    m_errorCount = 0;
    wxConfigBase * cfg = wxConfigBase::Get();
    cfg->SetPath(_T("/Serial"));
    const char * port = g_ports[cfg->Read(_T("Port"),1l)];
    wxBaud baud = g_bauds[cfg->Read(_T("Baud"),10l)];



    if (m_port.Open(port) < 0)
    {
        wxLogMessage(_("Unable to open port"));
        SignalState(false);
        return(false);
    }
    m_port.SetBaudRate(baud);
    int ct=0;
    while (!Ping())
    {
        ct++;
        if (ct == 5)
        {
            Close();
            return(false);
        }
    }
    SignalState(true);
    return(true);
}

void Serial::Close()
{
    SignalState(false);
    m_port.Close();
}

bool Serial::GetValue(const wxString& name, double & v)
{
    wxArrayString response;
    if (!Send(_T("g") + name, response))
    {
        return(false);
    }
    if (response.GetCount() == 1)
    {
        response[0].ToDouble(&v);
        EnableList(true);
        return(true);
    }
    return(false);
}

bool Serial::RunList(wxArrayString & list)
{
    EnableList(false);
    if(list.GetCount() ==0)
    {
        ClearList();
        return(true);
    }
    bool equal = false;
    if (m_list.GetCount() == list.GetCount())
    {
        equal = true;
        for (int ct=0; ct< m_list.GetCount(); ct++)
        {
            if (list[ct] != m_list[ct])
            {
                equal = false;
                break;
            }
        }
    }
    if (equal)
    {
        if (!SendR())
        {
            m_list.Clear();
            return(false);
        }
        m_running = true;
        return(true);
    }

    m_list.Clear();
    if (!Send(_T("c")))
    {
        return(false);
    }
    for (int ct = 0; ct< list.GetCount(); ct++)
    {
        if (!Send(_T("a") + list[ct]))
        {
            return(false);
        }
    }
    if (!SendR())
    {
        return(false);
    }
    m_running = true;
    m_list = list;
    return(true);
}

bool Serial::ListModules(wxArrayString& result)
{
    bool ret = Send(_T("l"),result);
    EnableList(ret);
    return(ret);
}

bool Serial::WriteEEPROM()
{
    bool ret = Send(_T("writeEEPROM"),4000); //use a long timeout because EEPRom may take a while
    EnableList(ret);
    return(ret);
}

bool Serial::SetValue(const wxString& s)
{
    bool ret = Send(_T("s") + s);
//    EnableList(ret);
    return(ret);
}

bool Serial::SetValue(const wxString& name, const wxString& value)
{
    bool ret = Send(_T("s") + name + _T("=") + value);
//    EnableList(ret);
    return(ret);
}

bool Serial::EnableList(const bool state)
{
    if (!state)
    {
        m_running = false;
//        m_list.Clear();
        return(true);
    }
    if (m_list.GetCount() ==0)
    {
        return(true);
    }
    m_running = true;
    return(SendR());
}

bool Serial::SendR()
{
    bool ret = RawSend(_T("r"),false);
//wxLogMessage(_T("r"));
    if(ret)
    {
        m_waiting = true;
    }
    return(ret);
}

bool Serial::Ping()
{
    bool ret = Send(_T("p"));
    EnableList(ret);
    return(ret);
}


bool Serial::GetVersion(wxString& version)
{
    bool ret = Send(_T("v"),version);
    EnableList(ret);
    return(ret);
}
